#include "sound/alsa_io.h"
#include "logger.h"
#include <iostream>

#define MAX_FRAMES 4096
#define MAX_CAPTURE_FRAMES 160
#define MAX_ERROR_COUNT 3

using namespace std;

CAlsaIO::CAlsaIO(EAction pAction, unsigned int pChannels)
		:mError(INIT_FAILURE),
		mAction(pAction),
		mPlaybackPrepareForFirstTime(true),
		mPlaybackChannels(0),
		mPlaybackHandle(NULL),
		mCapturePrepareForFirstTime(true),
		mCaptureChannels(pChannels),
		mCaptureHandle(NULL),
		mCaptureBuffer(NULL),
		mBytePerSample(0)
{
	mStreamInfo.mSamplesWidth = NAudioFormats::S16LE;
	mStreamInfo.mSampleRate = 44100;
	mStreamInfo.mCodec = NAudioFormats::PCM;

	mBytePerSample = getBytePerSample();

	if(!initCapture()) {
		return;
	}
	mError = INIT_NO_ERROR;
}

// for playback
CAlsaIO::CAlsaIO
(
		EAction pAction,
		unsigned int pChannels,
		NAudioFormats::SStreamInfo pStreamInfo
)
		:mError(INIT_FAILURE),
		mAction(pAction),
		mPlaybackPrepareForFirstTime(true),
		mPlaybackChannels(pChannels),
		mPlaybackHandle(NULL),
		mCapturePrepareForFirstTime(true),
		mCaptureChannels(0),
		mCaptureHandle(NULL),
		mStreamInfo(pStreamInfo),
		mBytePerSample(0)
{
	mBytePerSample = getBytePerSample();

	if(!initPlayback()) {
		return;
	}
	mError = INIT_NO_ERROR;
}

CAlsaIO::CAlsaIO
(
		EAction pAction
)
		:mError(INIT_FAILURE),
		mAction(pAction),
		mPlaybackPrepareForFirstTime(true),
		mPlaybackChannels(0),
		mPlaybackHandle(NULL),
		mCapturePrepareForFirstTime(true),
		mCaptureChannels(0),
		mCaptureHandle(NULL),
		mBytePerSample(0)
{
	switch(mAction) {
		case UNKNOWN_ACTION:
		case NO_ACTION:
			return;
		case EMBED:
		case EXTRACT:
			mPlaybackChannels = 1;
			mCaptureChannels = 1;
			break;
		case INTERLEAVED_EMBED_EXTRACT:
			mPlaybackChannels = 2;
			mCaptureChannels = 2;
			break;
	}
	mStreamInfo.mSamplesWidth = NAudioFormats::S16LE;
	mStreamInfo.mSampleRate = 44100;
	mStreamInfo.mCodec = NAudioFormats::PCM;

	mBytePerSample = getBytePerSample();

	if(!initPlayback()) {
		return;
	}
	if(!initCapture()) {
		return;
	}
	mError = INIT_NO_ERROR;
}

CAlsaIO::~CAlsaIO()
{
	if(mCaptureHandle) {
		snd_pcm_close(mCaptureHandle);
		mCaptureHandle = NULL;
	}
	if(mPlaybackHandle) {
		snd_pcm_close(mPlaybackHandle);
		mPlaybackHandle = NULL;
	}
}

int CAlsaIO::init(CPropertiesReader *pConfig)
{
	if(mError != INIT_NO_ERROR) {
		return -1;
	}
	return 0;
}

int CAlsaIO::destroy()
{
	int lErr;
	if(mCaptureHandle) {
		printl(0, __FILE__, "Stop capture.\n");
		lErr = snd_pcm_drain(mCaptureHandle);
		if(lErr < 0) {
			printl(LL_ERR, __FILE__, "snd_pcm_drain for capture failed. (%s)\n",
					snd_strerror (lErr));
		}
	}
	if(mPlaybackHandle) {
		//printl(0, __FILE__, "sleep 30.\n");
		//sleep(60);
		printl(0, __FILE__, "Wait for all pending frames to be played ...\n");
		// For playback wait for all pending frames to be played and then stop
		// the PCM. For capture stop PCM permitting to retrieve residual frames.
		lErr = snd_pcm_drain(mPlaybackHandle);
		if(lErr < 0) {
			printl(LL_ERR, __FILE__, "snd_pcm_drain for playback failed. (%s)\n",
					snd_strerror (lErr));
		}
		printl(0, __FILE__, "Now playback will be stopped.\n");
	}
	return 0;
}

int CAlsaIO::read(void** pData, EAction& pAction, int pTimeout)
{
	if(mError != INIT_NO_ERROR) {
		return -1;
	}
	if(!mCaptureBuffer || !mBytePerSample) {
		return -1;
	}

	int err;
	if(mCapturePrepareForFirstTime) {
		if ((err = snd_pcm_prepare (mCaptureHandle)) < 0) {
			printl(LL_ERR, __FILE__, "read: cannot prepare audio interface for use (%s)\n",
				snd_strerror (err));
			return -1;
		}
		mCapturePrepareForFirstTime = false;
	}

	int lReadCount;
	int lErrCount(0);
	while((lReadCount = snd_pcm_readi(mCaptureHandle,
			mCaptureBuffer,
			MAX_CAPTURE_FRAMES)) < 0) {
		if(lReadCount == -EPIPE) {
			printl(LL_ERR, __FILE__, "read: %s: Underrun occurred.\n",
					snd_strerror(lReadCount));
		}
		else if(lReadCount < 0) {
			printl(LL_ERR, __FILE__, "read: An error occured while reading sound data:\n%s\n",
					snd_strerror(lReadCount));
			return -1;
		}
		lErrCount++;
		if(lErrCount >= MAX_ERROR_COUNT) {
			printl(LL_ERR, __FILE__, "read: reached max error count.\n");
			return -1;
		}
		if ((err = snd_pcm_prepare (mCaptureHandle)) < 0) {
			printl(LL_ERR, __FILE__, "read: cannot prepare audio interface for use (%s)\n",
				snd_strerror (err));
			return -1;
		}
	}
	//printl(0, __FILE__, "read: readcount: %d/%d\n", lReadCount, MAX_CAPTURE_FRAMES);
	*pData = mCaptureBuffer;
	pAction = mAction;
	return lReadCount * mCaptureChannels * mBytePerSample;
}

int CAlsaIO::write(void* pData, const EAction& pAction, int pLength)
{
	if(mError != INIT_NO_ERROR) {
		return -1;
	}
	if(!mPlaybackChannels || !mPlaybackHandle || !mBytePerSample) {
		return -1;
	}

	/* the interface will interrupt the kernel every 4096 (MAX_FRAMES) frames,
	 * and ALSA will wake up this program very soon after that.
	 */

	int err;
	if(mPlaybackPrepareForFirstTime) {
		if ((err = snd_pcm_prepare (mPlaybackHandle)) < 0) {
			printl(LL_ERR, __FILE__, "write: cannot prepare audio interface for use (%s)\n",
				snd_strerror (err));
			return -1;
		}
		mPlaybackPrepareForFirstTime = false;
	}

	unsigned int fullFrameCount((pLength / mBytePerSample) / mPlaybackChannels);

	int lLoop(0); //only for tests
	int lErrCount;
	while (fullFrameCount > 0) {
		lLoop++;

		/* wait till the interface is ready for data, or 1 second has elapsed. */
		lErrCount = 0;
		while ((err = snd_pcm_wait (mPlaybackHandle, 1000)) < 0) {
			printl(LL_ERR, __FILE__, "write: poll failed (%s)\n", strerror (errno));
			lErrCount++;
			if(lErrCount >= MAX_ERROR_COUNT) {
				printl(LL_ERR, __FILE__, "write: reached max error count.\n");
				return -1;
			}
			if ((err = snd_pcm_prepare (mPlaybackHandle)) < 0) {
				printl(LL_ERR, __FILE__, "write: cannot prepare audio interface for use (%s)\n",
					snd_strerror (err));
				return -1;
			}
			printl(0, __FILE__, "write: repare audio interface\n");
		}

		/* find out how much space is available for playback data */
		snd_pcm_sframes_t frames_to_deliver;
		lErrCount = 0;
		while ((frames_to_deliver = snd_pcm_avail_update (mPlaybackHandle)) < 0) {
			if (frames_to_deliver == -EPIPE) {
				printl(LL_ERR, __FILE__, "write: an xrun occured\n");
			} else {
				printl(LL_ERR, __FILE__, "write: unknown ALSA avail update return value (%d)\n",
					(int)frames_to_deliver);
				return -1;
			}
			lErrCount++;
			if(lErrCount >= MAX_ERROR_COUNT) {
				printl(LL_ERR, __FILE__, "write: reached max error count.\n");
				return -1;
			}
			if ((err = snd_pcm_prepare (mPlaybackHandle)) < 0) {
				printl(LL_ERR, __FILE__, "write: cannot prepare audio interface for use (%s)\n",
					snd_strerror (err));
				return -1;
			}
			printl(0, __FILE__, "write: repare audio interface\n");
		}

		frames_to_deliver = frames_to_deliver > MAX_FRAMES ? MAX_FRAMES : frames_to_deliver;

		/* deliver the data */
		unsigned int frameCount(fullFrameCount);
		if(frameCount > static_cast<unsigned int>(frames_to_deliver)) {
			frameCount = frames_to_deliver;
		}

		lErrCount = 0;
		while ((err = snd_pcm_writei (mPlaybackHandle, pData, frameCount)) < 0) {
			printl(LL_ERR, __FILE__, "write: write failed (%s)\n", snd_strerror (err));
			lErrCount++;
			if(lErrCount >= MAX_ERROR_COUNT) {
				printl(LL_ERR, __FILE__, "write: reached max error count.\n");
				return -1;
			}
			if ((err = snd_pcm_prepare (mPlaybackHandle)) < 0) {
				printl(LL_ERR, __FILE__, "write: cannot prepare audio interface for use (%s)\n",
					snd_strerror (err));
				return -1;
			}
		}

		fullFrameCount -= frameCount;
		pData = static_cast<char *>(pData) + frameCount * mBytePerSample * mPlaybackChannels;
		//printl(0, __FILE__, "write: framecount: %u, loop: %d", frameCount, lLoop);
	}
	return pLength;
}

NAudioFormats::SStreamInfo CAlsaIO::getStreamInfo()
{
	return mStreamInfo;
}

bool CAlsaIO::initPlayback()
{
	snd_pcm_hw_params_t *hw_params;
	snd_pcm_sw_params_t *sw_params;
	//int nfds;
	int err;
	//struct pollfd *pfds;

	/* Open PCM device for playback. */
	if ((err = snd_pcm_open (&mPlaybackHandle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
		printl(LL_ERR, __FILE__, "cannot open audio device %s (%s)\n",
			"default",
			snd_strerror (err));
		return false;
	}

	/* Allocate a hardware parameters object. */
	if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot allocate hardware parameter structure (%s)\n",
			snd_strerror (err));
		return false;
	}

	/* Fill it in with default values. */
	if ((err = snd_pcm_hw_params_any (mPlaybackHandle, hw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot initialize hardware parameter structure (%s)\n",
			snd_strerror (err));
		return false;
	}

	/* Set the desired hardware parameters. */

	/* Interleaved mode */
	if ((err = snd_pcm_hw_params_set_access (mPlaybackHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set access type (%s)\n",
			snd_strerror (err));
		return false;
	}

	/* Signed 16-bit little-endian format */
	if ((err = snd_pcm_hw_params_set_format (mPlaybackHandle, hw_params, getAlsaSndPcmFormat())) < 0) {
		printl(LL_ERR, __FILE__, "cannot set sample format (%s)\n",
			snd_strerror (err));
		return false;
	}

	/* Two channels (stereo) */
	if(mPlaybackChannels != 1 && mPlaybackChannels != 2) {
		printl(LL_ERR, __FILE__, "wrong channel value\n");
		return false;
	}
	if ((err = snd_pcm_hw_params_set_channels (mPlaybackHandle, hw_params, mPlaybackChannels)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set channel count (%s)\n",
			snd_strerror (err));
		return false;
	}

	unsigned int rate = mStreamInfo.mSampleRate;
	/* 44100 samples/second - sampling rate (CD quality) */
	if ((err = snd_pcm_hw_params_set_rate_near (mPlaybackHandle, hw_params, &rate, 0)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set sample rate (%s)\n",
			snd_strerror (err));
		return false;
	}
	if(rate != mStreamInfo.mSampleRate) {
		printl(LL_ERR, __FILE__, "Wrong samplerate %u (%u).\n",
				rate,
				mStreamInfo.mSampleRate);
		return false;
	}

	/* Set period size to 32 frames. */ //TODO: for what?
	//frames = 32;
	//snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

	/* Write the parameters to the driver */
	if ((err = snd_pcm_hw_params (mPlaybackHandle, hw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set parameters (%s)\n",
			snd_strerror (err));
		return false;
	}

	snd_pcm_hw_params_free (hw_params);

	/* tell ALSA to wake us up whenever 4096 (MAX_FRAMES) or more frames
		of playback data can be delivered. Also, tell
		ALSA that we'll start the device ourselves.
	*/

	if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot allocate software parameters structure (%s)\n",
			snd_strerror (err));
		return false;
	}
	if ((err = snd_pcm_sw_params_current (mPlaybackHandle, sw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot initialize software parameters structure (%s)\n",
			snd_strerror (err));
		return false;
	}
	if ((err = snd_pcm_sw_params_set_avail_min (mPlaybackHandle, sw_params, MAX_FRAMES)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set minimum available count (%s)\n",
			snd_strerror (err));
		return false;
	}
	if ((err = snd_pcm_sw_params_set_start_threshold (mPlaybackHandle, sw_params, 0U)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set start mode (%s)\n",
			snd_strerror (err));
		return false;
	}

	/* Write the software parameters */
	if ((err = snd_pcm_sw_params (mPlaybackHandle, sw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set software parameters (%s)\n",
			snd_strerror (err));
		return false;
	}

	//TODO: is this ok???
	snd_pcm_sw_params_free (sw_params);

	return true;
}

bool CAlsaIO::initCapture()
{
	int err;
	snd_pcm_hw_params_t *hw_params;

	/* Open PCM device for playback. */
	if ((err = snd_pcm_open (&mCaptureHandle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) {
		printl(LL_ERR, __FILE__, "cannot open audio device %s (%s)\n",
			"default",
			snd_strerror (err));
		return false;
	}

	/* Allocate a hardware parameters object. */
	if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot allocate hardware parameter structure (%s)\n",
			snd_strerror (err));
		return false;
	}

	/* Fill it in with default values. */
	if ((err = snd_pcm_hw_params_any (mCaptureHandle, hw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot initialize hardware parameter structure (%s)\n",
			snd_strerror (err));
		return false;
	}

	/* Set the desired hardware parameters. */

	/* Interleaved mode */
	if ((err = snd_pcm_hw_params_set_access (mCaptureHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set access type (%s)\n",
			snd_strerror (err));
		return false;
	}

	/* Signed 16-bit little-endian format */
	if ((err = snd_pcm_hw_params_set_format (mCaptureHandle, hw_params, getAlsaSndPcmFormat())) < 0) {
		printl(LL_ERR, __FILE__, "cannot set sample format (%s)\n",
			snd_strerror (err));
		return false;
	}

	unsigned int rate = mStreamInfo.mSampleRate;
	if ((err = snd_pcm_hw_params_set_rate_near (mCaptureHandle, hw_params, &rate, 0)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set sample rate (%s)\n",
			snd_strerror (err));
		return false;
	}
	if(rate != mStreamInfo.mSampleRate) {
		printl(LL_ERR, __FILE__, "Wrong samplerate %u (%u).\n",
				rate,
				mStreamInfo.mSampleRate);
		return false;
	}

	/* Two channels (stereo) */
	if(mCaptureChannels != 1 && mCaptureChannels != 2) {
		printl(LL_ERR, __FILE__, "wrong capture channel value\n");
		return false;
	}
	if ((err = snd_pcm_hw_params_set_channels (mCaptureHandle, hw_params, mCaptureChannels)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set channel count (%s)\n",
			snd_strerror (err));
		return false;
	}

	if ((err = snd_pcm_hw_params (mCaptureHandle, hw_params)) < 0) {
		printl(LL_ERR, __FILE__, "cannot set parameters (%s)\n",
			snd_strerror (err));
		return false;
	}

	snd_pcm_hw_params_free (hw_params);

	if(mBytePerSample == 0) {
		printl(LL_ERR, __FILE__, "Byte per sample can't be 0.\n");
		return false;
	}
	mCaptureBuffer = new char[MAX_CAPTURE_FRAMES * mBytePerSample * mCaptureChannels];
	return true;
}

unsigned int CAlsaIO::getBytePerSample()
{
	switch(mStreamInfo.mSamplesWidth) {
		case NAudioFormats::UNKNOWN_WIDTH:
			printl(LL_ERR, __FILE__, "Unknown sample width.\n");
			return 0;
		case NAudioFormats::S8:
		case NAudioFormats::U8:
			return 1;
		case NAudioFormats::S16LE:
		case NAudioFormats::S16BE:
		case NAudioFormats::S16HO:
		case NAudioFormats::U16LE:
		case NAudioFormats::U16BE:
		case NAudioFormats::U16HO:
			return 2;
		case NAudioFormats::DOUBLE:
			printl(LL_WARN, __FILE__, "Sample width for double is used.\n");
			return (sizeof(double) + 7) / 8;
	}
	printl(LL_ERR, __FILE__, "Unsupported sample width.\n");
	return 0;
}

snd_pcm_format_t CAlsaIO::getAlsaSndPcmFormat()
{
	switch(mStreamInfo.mCodec) {
		case NAudioFormats::UNKNOWN_CODEC:
			printl(LL_ERR, __FILE__, "Unknown codec.\n");
			break;
		case NAudioFormats::PCM:
			switch(mStreamInfo.mSamplesWidth) {
				case NAudioFormats::UNKNOWN_WIDTH:
					printl(LL_ERR, __FILE__, "Unknown sample width for PCM.\n");
					break;

				case NAudioFormats::S8:
					return SND_PCM_FORMAT_S8;
				case NAudioFormats::U8:
					return SND_PCM_FORMAT_U8;

				case NAudioFormats::S16LE:
					return SND_PCM_FORMAT_S16_LE;
				case NAudioFormats::S16BE:
					return SND_PCM_FORMAT_S16_BE;
				case NAudioFormats::S16HO:
					return SND_PCM_FORMAT_S16;

				case NAudioFormats::U16LE:
					return SND_PCM_FORMAT_U16_LE;
				case NAudioFormats::U16BE:
					return SND_PCM_FORMAT_U16_BE;
				case NAudioFormats::U16HO:
					return SND_PCM_FORMAT_U16;

				case NAudioFormats::DOUBLE:
					return SND_PCM_FORMAT_FLOAT64;
			}
			break;
		case NAudioFormats::ALAW:
			switch(mStreamInfo.mSamplesWidth) {
				case NAudioFormats::UNKNOWN_WIDTH:
					printl(LL_ERR, __FILE__, "Unknown sample width for ALAW.\n");
					break;
				case NAudioFormats::S8:
				case NAudioFormats::U8:
					return SND_PCM_FORMAT_A_LAW;
				default:
					printl(LL_ERR, __FILE__, "Unsupported sample width for ALAW.\n");
					break;
			}
			break;
		case NAudioFormats::ULAW:
			switch(mStreamInfo.mSamplesWidth) {
				case NAudioFormats::UNKNOWN_WIDTH:
					printl(LL_ERR, __FILE__, "Unknown sample width for ULAW.\n");
					break;
				case NAudioFormats::S8:
				case NAudioFormats::U8:
					return SND_PCM_FORMAT_MU_LAW;
				default:
					printl(LL_ERR, __FILE__, "Unsupported sample width for ULAW.\n");
					break;
			}
			break;
	}
	return SND_PCM_FORMAT_UNKNOWN;
}
